/*
This file is part of MMM.

MMM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

MMM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with MMM.  If not, see <http://www.gnu.org/licenses/>.
*
* @package    MMMTools
* @copyright  2015 High Performance Humanoid Technologies (H2T), Karlsruhe, Germany
*
*/

#include <boost/extension/shared_library.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/function.hpp>

#include <MMM/Model/ModelProcessorFactory.h>
#include <MMM/Model/ModelReaderXML.h>
#include <MMM/Motion/Legacy/LegacyMotionReaderC3D.h>
#include <MMM/Motion/Legacy/LegacyMotionReaderXML.h>

#include "ConverterApplicationHelper.h"

#ifdef WIN32
#define MMMConverter_LIB_EXTENSION ".dll"
#else
#define MMMConverter_LIB_EXTENSION ".so"
#endif

std::vector<MMM::ConverterFactoryPtr> checkForFactories(const std::vector<std::string>& converterLibSearchPaths)
{
    std::vector<MMM::ConverterFactoryPtr> result;

    for (std::vector<std::string>::const_iterator dirIt = converterLibSearchPaths.begin(); dirIt != converterLibSearchPaths.end(); ++dirIt)
    {
        std::string lp = *dirIt;
        MMM_INFO << "Searching for converters at: " << lp << std::endl;

        boost::filesystem::path targetDir(lp);

        if (!is_directory(targetDir))
        {
            MMM_WARNING << "Path " <<  lp << " is not a directory!" << std::endl;
            continue;
        }

        boost::filesystem::directory_iterator it(targetDir), eod;
        BOOST_FOREACH(boost::filesystem::path const &p, std::make_pair(it, eod))
        {
            if (p.extension().string() == MMMConverter_LIB_EXTENSION && is_regular_file(p))
            {
                boost::extensions::shared_library lib(p.string());
                if (lib.open())
                {
                    boost::function<boost::shared_ptr<MMM::ConverterFactory>()> getFactory;
                    getFactory = lib.get<boost::shared_ptr<MMM::ConverterFactory> >("getFactory");
                    if (getFactory)
                    {
                        MMM::ConverterFactoryPtr converterFactory = getFactory();
                        result.push_back(converterFactory);
                    }
                }
                else
                {
                    MMM_INFO << "Could not open lib " << p.string() << "!" << std::endl;
                    MMM_INFO << "The error message was: " << dlerror() << std::endl;
                }
            }
        }
    }

    return result;
}

void setupConverterFactories(const std::vector<std::string>& converterLibSearchPaths)
{
    checkForFactories(converterLibSearchPaths);
    std::vector<std::string> factoryList = MMM::ConverterFactory::getSubclassList();

    MMM_INFO << "Available converter factories:" << std::endl;
    for (std::vector<std::string>::const_iterator i = factoryList.begin(); i != factoryList.end(); ++i)
        MMM_INFO << " * " << *i << std::endl;
}

MMM::ModelPtr readModel(const std::string& modelFile)
{
    MMM::ModelPtr model;

    if (!modelFile.empty())
    {
        MMM::ModelReaderXMLPtr r(new MMM::ModelReaderXML());
        model = r->loadModel(modelFile);

        if (!model)
            MMM_ERROR << "Could not read model from " << modelFile << "!" << std::endl;
    }

    return model;
}

MMM::MarkerMotionPtr readMarkerMotion(const std::string& filePath, const std::string& markerPrefix)
{
    MMM::LegacyMotionReaderC3DPtr r(new MMM::LegacyMotionReaderC3D());
    r->markerPrefix = markerPrefix;

    MMM::MarkerMotionPtr markerMotion = r->loadC3D(filePath);
    if (!markerMotion)
        MMM_ERROR << "Could not load motion from " << filePath << "!" << std::endl;

    return markerMotion;
}

MMM::LegacyMotionPtr readMMMMotion(const std::string& filePath)
{
    MMM::LegacyMotionReaderXMLPtr r(new MMM::LegacyMotionReaderXML());
    MMM::LegacyMotionPtr mmmMotion = r->loadMotion(filePath);
    if (!mmmMotion)
        MMM_ERROR << "Could not load motion from " << filePath << "!" << std::endl;

    return mmmMotion;
}

MMM::ModelProcessorPtr setupModelProcessor(const std::string& modelProcessorName, const std::string& modelProcessorConfigFile)
{
    MMM::ModelProcessorFactoryPtr modelFactory = MMM::ModelProcessorFactory::fromName(modelProcessorName, NULL);
    if (!modelFactory)
    {
        MMM_ERROR << "Could not create model processing factory of type \"" << modelProcessorName << "\"!" << std::endl;
        return MMM::ModelProcessorPtr();
    }

    MMM::ModelProcessorPtr modelProcessor = modelFactory->createModelProcessor();
    if (!modelProcessor)
    {
        MMM_ERROR << "Could not create model processor!" << std::endl;
        return MMM::ModelProcessorPtr();
    }

    if (!modelProcessorConfigFile.empty())
    {
        if (!modelProcessor->setupFile(modelProcessorConfigFile))
        {
            MMM_ERROR << "Error while configuring model processor \"" << modelProcessorName << "\" from file \"" << modelProcessorConfigFile << "\"!" << std::endl;
            return MMM::ModelProcessorPtr();
        }
    }

    return modelProcessor;
}

MMM::ConverterPtr createConverter(const std::string& converterName, const std::string& converterConfigFile)
{
    boost::shared_ptr<MMM::ConverterFactory> converterFactory = MMM::ConverterFactory::fromName(converterName, NULL);
    if (!converterFactory)
    {
        MMM_ERROR << "Could not create converter factory of type \"" << converterName << "\"!" << std::endl;
        return MMM::ConverterPtr();
    }

    MMM::ConverterPtr converter = converterFactory->createConverter();
    if (!converter)
    {
        MMM_ERROR << "Could not create converter!" << std::endl;
        return MMM::ConverterPtr();
    }

    if (!converterConfigFile.empty())
    {
        if (!converter->setupFile(converterConfigFile))
        {
            MMM_ERROR << "Error while configuring converter \"" << converterName << "\" from file \"" << converterConfigFile << "\"!" << std::endl;
            return MMM::ConverterPtr();
        }
    }

    return converter;
}
